;;; ====
;;; GUIX
;;; ====

;; Load packages, which are installed via guix.

(setq guix-package-enable-at-startup t)
(require 'guix-init nil t)

;;; =====
;;; MELPA
;;; =====

;; This adds the melpa stable repository to the list of package
;; archives.

(require 'package)
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)

;;; =====================
;;; PACKAGE CONFIFURATION
;;; =====================

;;; use-package install

;; (eval-when-compile
;;   ;; Following line is not needed if use-package.el is in ~/.emacs.d
;;   (add-to-list 'load-path "<path where use-package is installed>")
;;   (require 'use-package))

(require 'use-package)


(defun string-length= (a b)
  (= (length a)
     (length b)))


(defun string-length< (a b)
  (= (length a)
     (length b)))


(defun xiaolong/ivy--files-sort-by-length (x y)
  "Sort given filenames X and Y according to string length.
However, give directories priority."
  (let ((cleaned-x (if (string-suffix-p "/" x) (substring x 0 -1) x))
        (cleaned-y (if (string-suffix-p "/" y) (substring y 0 -1) y)))
    (let* ((length-equal? (string-length= cleaned-x cleaned-y))
           (x-shorter? (string-length< cleaned-x cleaned-y))
           (x-wins t)
           (y-wins nil))
      (let ((result
             ;; compare
             (if (file-directory-p cleaned-x)
                 (if (file-directory-p cleaned-y)
                     ;; If both are a directory, and of same length, let the
                     ;; default comparison function decide.
                     (if length-equal?
                         (ivy-sort-file-function-default x y)
                       ;; If not of same length, then check if x is shorter and
                       ;; prefer x if it is shorter.
                       x-shorter?)
                   ;; Prefer x if y is not a directory.
                   x-wins)
               (if (file-directory-p cleaned-y)
                   y-wins
                 ;; If both are a directory, and of same length, let the
                 ;; default comparison function decide.
                 (if length-equal?
                     (ivy-sort-file-function-default x y)
                   ;; If not of same length, then check if x is shorter and
                   ;; prefer x if it is shorter.
                   x-shorter?)))))
        result))))


(defun xiaolong/ivy--commands-sort-by-length (x y)
  "Sort given filenames X and Y according to string length.
However, give directories priority."
  (let* ((length-equal? (string-length= x y))
         (x-shorter? (string-length< x y)))
    (let ((result
           (if length-equal?
               (...default... x y)
             ;; If not of same length, then check if x is shorter and
             ;; prefer x if it is shorter.
             x-shorter?)))
      result)))


(use-package counsel
  :ensure t
  :bind
  (("C-x C-f" . #'counsel-find-file)
   ("C-x f" . #'counsel-find-file)
   ;; ("M-x" . #'counsel-M-x-with-last-command)
   ;; ("M-x" . #'command-execute)
   ;; ("M-x" . (lambda () (interactive) (execute-extended-command)))
   ("M-x" . #'counsel-M-x)
   ("C-x b" . #'counsel-switch-buffer))
  :config
  ;; (use-package flx
  ;;   :ensure t)
  (ivy-mode 1)
  (setq ivy-height 10)
  (setq ivy-re-builders-alist '((t . ivy--regex-fuzzy)))
  ;; Remove the leading `^` from the input of counsel-M-x.
  (setcdr (assoc 'counsel-M-x ivy-initial-inputs-alist) "")
  ;; Add our own sort function to the association list of sort functions.
  (add-to-list 'ivy-sort-functions-alist
               '(read-file-name-internal . xiaolong/ivy--files-sort-by-length)))


(use-package swiper
  :ensure t
  :bind
  (("C-s" . 'swiper))
  :config
  (ivy-mode 1)
  (setq ivy-use-virtual-buffers t)
  (setq enable-recursive-minibuffers t))


;;; BASH

(setq shell-file-name "bash")
(setq shell-command-switch "-c")


;; Use sh instead of GNU Bash. This is done as an attempt to
;; get literate programming working for GNU Guile.
;; (setq shell-file-name "sh")
;; (setq shell-command-switch "-c")


;; (setq explicit-shell-file-name "/bin/sh")

;;; TRAMP

(setq tramp-default-method "ssh")
(setq tramp-verbose 5)
(setq password-cache-expiry 86400)  ; 1 day
(setq auth-source-debug t)
(setq auth-sources nil)

;;; GEISER

(require 'geiser)
(require 'geiser-guile)
(setq geiser-guile-binary "guile")
;; (setq geiser-guile-load-init-file-p t)
(setq geiser-default-implementation 'guile)
(setq geiser-scheme-implementation 'guile)
(setq geiser-active-implementations '(guile))

(defun my-run-guile ()
  (interactive)
  (let ((shell-file-name "bash")
        (shell-command-switch "-ic"))
    (run-guile)))

(setq geiser-guile-warning-level 'high)

;;; SCHEME

(setq scheme-program-name "guile")

(with-eval-after-load 'info-look
  (info-lookup-add-help
   :mode 'scheme-mode
   :regexp "[^()`'‘’,\" \t\n]+"
   ;; :doc-spec describes the pages where info-look can find
   ;; and how to parse the index of symbols for the
   ;; specified mode
   :doc-spec '(("(guile) Procedure Index" nil nil nil)
               ("(guile) Variable Index" nil nil nil)
               ("(guile) R5RS Index" nil nil nil)
               ("(guile) Type Index" nil nil nil)
               ("(guile) Concept Index" nil nil nil)
               ("(r5rs)Index" nil "^[   ]+-+ [^:]+:[    ]*" "\\b"))))

;; You can find what is already defined by looking up the
;; info-lookup-alist variable.

;;; ORG

(setq org-src-fontify-natively t
      org-src-window-setup 'current-window ;; edit in current window
      org-src-strip-leading-and-trailing-blank-lines t
      org-src-preserve-indentation t ;; do not put two spaces on the left
      org-src-tab-acts-natively t)

;; allow usage of shift in some situations
(setq org-support-shift-select t)

;; agenda files
(setq org-agenda-files '("/home/xiaolong/dev/private-notes/birthdays.org"))

;; The following enables source blocks of specific
;; programming languages to be executed. By default only
;; elisp is allowed to be executed. First we define an
;; association list of languages to booleans, which enable
;; or disable support for a language.
(setq org-babel-load-languages
      '((emacs-lisp . t)
        ;; (elisp . t)
        ;; (erlang . t)
        ;; (python . t)
        (prolog . t)
        (scheme . t)
        (shell . t)
        ;; (haskell . t)
        ))

;; Then we use org-babel-do-load-languages to actually tell
;; org babel, that the languages should be supported.
(org-babel-do-load-languages
 'org-babel-load-languages
 '((prolog . t)
   (scheme . t)
   (shell . t)
   ;; (emacs-lisp . t)
   ))

;; The following defines a procedure, which determins, which
;; source blocks of programming languages can be run without
;; confirmation, depending on the language. The procedure
;; can then be used as org-confirm-babel-evaluate value.
(defun xiaolong/org-confirm-babel-evaluate (lang body)
  (not
   (or (string= lang "scheme")
       ;; (string= lang "emacs-lisp")
       ;; (string= lang "elisp")
       ;; (string= lang "python")
       (string= lang "shell")
       )))

;; Set a procedure predicate for org babel, to determin, which languages can
;; be run without confirmation.
(setq org-confirm-babel-evaluate 'xiaolong/org-confirm-babel-evaluate)

;;; EXPORT

(add-hook 'org-mode-hook
          (function
           (lambda ()
             (require 'ox-gfm))))

;;; COMPANY

(setq company-idle-delay t)
(setq company-require-match nil)
(setq company-show-numbers t)
(setq company-tooltip-idle-delay nil)
(setq company-tooltip-maximum-width 120)
(setq company-tooltip-minimum 3)
(setq company-tooltip-align-annotations t)

;;; EDITORCONFIG

;; Editorconfig uses a standardized configuration file to
;; unify formatting across editors, which have support for
;; editorconfig, usually via some plugin or extension.

(editorconfig-mode 1)

;;; MARKDOWN

(add-hook 'markdown-mode-hook
          (function
           (lambda ()
             (setq whitespace-style (delete 'lines-tail whitespace-style)))))

;;; FLYCHECK

;; Currently no config for flycheck. Seems to interfere with
;; line-reminder-mode.

;;; RACKET

(add-to-list 'auto-mode-alist '("\\.rkt\\'" . racket-mode))
(add-hook 'racket-mode-hook
          (function
           (lambda ()
             (setq indent-tabs-mode nil tab-width 2))))

;;; IO LANG

(add-to-list 'load-path "~/.emacs.d/packages/io-mode")
(require 'io-mode)

;;; PROLOG

(require 'prolog)
(add-to-list 'auto-mode-alist '("\\.pl\\'" . prolog-mode))

;;; GFORTH

;;; UNDO TREE

(global-undo-tree-mode)
(setq undo-tree-auto-save-history nil)

;;; ESHELL EXEC PATH FROM SHELL

(when (memq window-system '(mac ns x))
  (exec-path-from-shell-initialize))

;;; NEOTREE

(setq neo-autorefresh nil)
(setq neo-persist-show nil)
(setq neo-show-hidden-files nil)
(setq neo-smart-open t)
(setq neo-theme 'ascii)
(setq neo-vc-integration '(char))
(setq neo-window-fixed-size nil)

;;; YAML MODE

(add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode))
(add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-mode))
(add-hook 'yaml-mode-hook
          #'(lambda ()
              (define-key yaml-mode-map "\C-m" 'newline-and-indent)))

;;; YASNIPPET

(yas-global-mode 1)
(setq yas-snippet-dirs
      (append yas-snippet-dirs
              '("~/.emacs.d/snippets")))

;;; =====================
;;; GENERAL CONFIGURATION
;;; =====================

;;; TOOLBAR

(tool-bar-mode -1)

;;; SESSION

(setq desktop-dirname             (concat user-emacs-directory "/desktop/")
      desktop-base-file-name      "emacs.desktop"
      desktop-base-lock-name      "lock"
      desktop-path                (list desktop-dirname)
      desktop-save                t
      desktop-files-not-to-save   "^$" ;reload tramp paths
      desktop-load-locked-desktop nil
      desktop-auto-save-timeout   5)

;;; SAVE DESKTOP

(desktop-save-mode 1)

;;; FONT

;; Set default font.

(let ((standard-font "DejaVu Sans Mono:weight=normal:height=110:antialias=1"))
  (set-frame-font standard-font))

;; (let ((standard-font "Monocraft:weight=medium:height=110:antialias=0"))
;;   (set-frame-font standard-font))

(set-face-attribute 'default nil :height 110)

;; A fontset font seems to be a font, which is used for a specific set of
;; characters, which can be associated with a particular language. Using fontset
;; font settings, it should be possible to set a font per langugage, as is
;; probably done on the Emacs HELLO page.

(let ((chinese-font "WenQuanYi Micro Hei Mono:weight=normal:height=140:antialias=1"))
  (dolist (charset '(kana han symbol cjk-misc bopomofo))
    (set-fontset-font "fontset-default"
                      charset
                      chinese-font)))

;;; MATCHING PARENS

;; highlight matching parenthesis
(show-paren-mode 1)
(setq-default show-paren-delay 0) ;; 0 delay
(setq-default show-paren-style 'parenthesis) ;;'parenthesis is another possible value, only highlighting the brackets

;;; TABS AND SPACES

(setq-default tab-width 4)
(setq-default indent-tabs-mode nil)
(setq-default tab-stop-list (number-sequence 4 200 4))

;;; WHITESPACE

(add-hook 'before-save-hook 'delete-trailing-whitespace)
(add-hook 'before-save-hook 'whitespace-cleanup)

;;; SCROLL BEHAVIOR

(setq-default mouse-wheel-scroll-amount '(5 ((shift) . 5))) ;; two lines at a time
(setq-default mouse-wheel-progressive-speed nil) ;; don't accelerate scrolling
(setq-default mouse-wheel-follow-mouse 't) ;; scroll window under mouse
(setq-default scroll-step 5) ;; keyboard scroll one line at a time
(scroll-bar-mode -1)
(setq-default scroll-conservatively 10000)
;; (setq-default scroll-margin 4)

;;; INITIAL FRAME RESIZING

(setq frame-inhibit-implied-resize t)

;;; DELETE SELECTED TEXT WHEN TYPING

(delete-selection-mode 1)

;;; COMPLETION FUNCTION PREVALENCE

(setq hippie-expand-try-functions-list
  '(try-expand-dabbrev
    try-expand-dabbrev-all-buffers
    try-expand-dabbrev-from-kill
    ;; try-complete-file-name-partially
    ;; try-complete-file-name
    try-expand-all-abbrevs
    ;; try-expand-list
    ;; try-expand-line
    try-complete-lisp-symbol-partially
    ;;try-complete-lisp-symbol)
     ))

;;; PREFERRED ENCODING

(prefer-coding-system 'utf-8)
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)

;;; FRINGE

;; previous config:

(fringe-mode '(8 . 1))

;;; BACKUP FILES

(setq backup-directory-alist `(("." . "~/.emacs-backups")))
(setq backup-by-copying t)
(setq delete-old-versions t
      kept-new-versions 6
      kept-old-versions 10
      version-control t)

;;; TERMINAL

(setq ansi-color-for-comint-mode-on t)
;; vterm
(setq vterm-ignore-blink-cursor t)
(setq vterm-max-scrollback 100000)

;;; CALENDAR

(setq calendar-holidays nil)

;;; LINE NUMBERS

;; copied from: https://www.emacswiki.org/emacs/LineNumbers#h5o-1

;; (defcustom display-line-numbers-exempt-modes
;;   '(vterm-mode eshell-mode shell-mode term-mode ansi-term-mode org-mode)
;;   "Major modes on which to disable line numbers."
;;   :group 'display-line-numbers
;;   :type 'list
;;   :version "green")

;; (defun display-line-numbers--turn-on ()
;;   "Turn on line numbers except for certain major modes.
;; Exempt major modes are defined in
;; `display-line-numbers-exempt-modes'."
;;   (unless (or (minibufferp)
;;               (member major-mode display-line-numbers-exempt-modes))
;;     (display-line-numbers-mode)))

;; (global-display-line-numbers-mode)

;; only in programming modes
;; (add-hook 'prog-mode-hook (function (lambda () (display-line-numbers-mode))))

(defun xiaolong/line-numbers ()
  (interactive)
  (display-line-numbers-mode 'toggle))

;;; COLUMN NUMBERS

(column-number-mode)

;;; PDF VIEWING

(setq-default doc-view-pdfdraw-program "mudraw")
(setq-default doc-view-continuous t)

;;; MINIBUFFER COMPLETION

;; case-insensitive minibuffer completion
(setq read-buffer-completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)

;; ESHELL

(defun shorten-filename (filename limit)
  (let ((loc (if (string= filename (getenv "HOME"))
                 "~"
                 filename)))
    (let ((loc-length (length loc)))
      (if (> loc-length limit)
          (concat "..." (substring loc (- loc-length limit)))
          loc))))

;; change eshell prompt
(defun set-eshell-prompt-function ()
  (setq eshell-prompt-function
        (function
         (lambda ()
           (concat
            (propertize (format-time-string "%H:%M:%S" (current-time))
                        'face
                        `(:foreground "#A0A0A0"))
            "::"
            ;; (propertize (concat "[" (number-to-string (eshell-last-command-status)) "]")
            ;;             'face
            ;;             `(:foreground "#A0A0A0"))
            ;; "::"
            ;; (propertize (concat "[" (number-to-string $?) "]")
            ;;             'face
            ;;             `(:foreground "#A0A0A0"))
            ;; "::"
            (propertize (user-login-name)
                        'face
                        `(:foreground "#FF9080"))
            "@"
            (propertize (let ((sys-name (system-name)))
                          (let ((sys-name-len (length sys-name)))
                            (if (> sys-name-len 8)
                                (concat (substring sys-name 0 8) "...")
                              sys-name)))
                        'face
                        `(:foreground "#FF9080"))
            "::"
            (propertize (shorten-filename (eshell/pwd) 36)
                        'face
                        `(:foreground "#FF9080"))
            "::"
            (propertize (concat "("
                                (if-let ((status eshell-last-command-status))
                                    (if (= status 0)
                                        "0"
                                        (format "%s" status)))
                                ")")
                        'face
                        `(:foreground "#FF9080"))
            (if (= (user-uid) 0)
                "# "
                "$ ")))))
  (setq eshell-highlight-prompt t)
  ;; The prompt is:
  ;; + anything except '#', '$', and '\n' an arbitrary number of times followed by
  ;; + '#' or '$' followed by
  ;; + ' '.
  (setq eshell-prompt-regexp "^[^#$\n]*[#$] "))

(set-eshell-prompt-function)

;;; =================
;;; PROGRAMMING MODES
;;; =================

(require 'indicators)
;; (use-package line-reminder
;;   :ensure t
;;   :init
;;   (setq line-reminder-show-option 'indicators)
;;   ;; Customize the modified sign.
;;   ;; (setq line-reminder-modified-sign "▐")
;;   ;;Customize the saved sign.
;;   ;; (setq line-reminder-saved-sign "▐")
;;   ;;Customize string on the right/left side of the line number.
;;   (setq line-reminder-linum-left-string "")
;;   (setq line-reminder-linum-right-string " ")
;;   :config
;;   (custom-set-faces
;;    '(line-reminder-modified-sign-face ((t (:foreground "gold"))))
;;    '(line-reminder-saved-sign-face ((t (:foreground "#60FA6A")))))
;;   (add-hook 'prog-mode-hook
;;             (function
;;              (lambda ()
;;                (line-reminder-mode t)
;;                (setq line-reminder-show-option 'indicators)))))



;;; ============
;;; KEY BINDINGS
;;; ============

;;; UNSET SOME

(dolist (key '("\C-g")
             '("\C-x f"))
  (global-unset-key key))

(dolist (key '("\C-k"))
  (global-unset-key key))

;; Avoid annoying exit shortcut.
(global-unset-key (kbd "C-x C-c"))

;;; DEFINE MY OWN

(define-key global-map (kbd "RET") 'newline-and-indent)
(define-key global-map (kbd "<C-return>") 'newline)
(global-set-key (kbd "<C-mouse-5>")
  (lambda () (interactive) (text-scale-decrease 1)))
(global-set-key (kbd "<C-mouse-4>")
  (lambda () (interactive) (text-scale-increase 1)))
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)  ; TODO: remove
(global-set-key (kbd "C-n") 'set-mark-command)
(global-set-key (kbd "C->") 'xiaolong/indent-region)
(global-set-key (kbd "C-<") 'xiaolong/unindent-region)
(global-set-key (kbd "<C-tab>") 'hippie-expand)

(global-set-key (kbd "C-~") 'xiaolong/toggle-case)
(global-set-key (kbd "C-d") 'kill-whole-line)
(global-set-key (kbd "M-<left>") 'backward-sexp)
(global-set-key (kbd "M-<right>") 'forward-sexp)
;; moving s-expressions
(global-set-key (kbd "C-c C-<right>") 'transpose-sexps)
(global-set-key (kbd "C-c C-<left>") 'xiaolong/reverse-transpose-sexps)
;; moving between windows
(global-set-key (kbd "s-<left>") 'windmove-left)
(global-set-key (kbd "s-<right>") 'windmove-right)
(global-set-key (kbd "s-<up>") 'windmove-up)
(global-set-key (kbd "s-<down>") 'windmove-down)
;; rebinding combination for zap-to-char to the installed zop-to-char
(global-set-key [remap zap-to-char] 'zop-up-to-char)  ; TODO: move to use-package def

;; spelling change dictionary
(global-set-key (kbd "C-c d") 'switch-dictionary-de-en)
(global-set-key (kbd "C-c S-h") 'highlight-regexp)
(global-set-key (kbd "C-c h") 'highlight-phrase)
(global-set-key (kbd "C-c u") 'unhighlight-regexp)
;; multiple cursor bindings with ctrl+c and m
(global-set-key (kbd "C-c m") 'set-rectangular-region-anchor)
;; reload file
(global-set-key (kbd "C-c r") 'xiaolong/revert-buffer-no-confirm)
(global-set-key (kbd "C-c a") 'org-agenda)
;; wrap selection in enclosing characters
(global-set-key (kbd "C-c w (") 'insert-pair)
(global-set-key (kbd "C-c w [") 'insert-pair)
(global-set-key (kbd "C-c w {") 'insert-pair)
(global-set-key (kbd "C-c w \"") 'insert-pair)
(global-set-key (kbd "C-c w \'") 'insert-pair)
;; move windows (buffers)
;; (global-set-key (kbd "M-s-<up>") 'windmove-up)
;; (global-set-key (kbd "M-s-<down>") 'windmove-down)
;; (global-set-key (kbd "M-s-<left>") 'windmove-left)
;; (global-set-key (kbd "M-s-<right>") 'windmove-right)
;; text moving
(global-set-key (kbd "M-<up>") 'move-text-up)
(global-set-key (kbd "M-<down>") 'move-text-down)
;; buffers
(global-set-key (kbd "C-x a k") 'xiaolong/close-all-buffers)
(global-set-key (kbd "C-x a o") 'xiaolong/kill-other-buffers)
(global-set-key (kbd "C-k") 'kill-this-buffer)
(global-set-key (kbd "C-x f") 'find-file)
(global-set-key (kbd "C-a") 'mark-whole-buffer)
;; zooming
(global-set-key (kbd "C-+") 'text-scale-increase)
(global-set-key (kbd "C--") 'text-scale-decrease)

;; company mode keys
(global-set-key (kbd "C-c C-f") 'xiaolong/beautify-json)

;; make use of additional mouse keys
(global-set-key (kbd "<drag-mouse-8>") 'previous-buffer)
(global-set-key (kbd "<drag-mouse-9>") 'next-buffer)
(global-set-key (kbd "<mouse-8>") 'previous-buffer)
(global-set-key (kbd "<mouse-9>") 'next-buffer)

(global-set-key (kbd "C-c t") 'toggle-truncate-lines)

(global-set-key (kbd "C-g l") 'goto-last-change)

(global-set-key (kbd "<f8>") 'neotree-toggle)

;; allow input of dead keys

(define-key key-translation-map [dead-grave] "`")
(define-key key-translation-map [dead-acute] "'")
(define-key key-translation-map [dead-circumflex] "^")
(define-key key-translation-map [dead-diaeresis] "\"")
(define-key key-translation-map [dead-tilde] "~")

;;; ================
;;; CUSTOM FUNCTIONS
;;; ================

;;; KEYBOARD MACROS

(defun xiaolong/save-macro (name)
  "Save a macro.  Take a NAME as argument and save the last
defined macro under this name at the end of your .emacs."
  (interactive "SName of the macro: ")  ; ask for the name of the macro
  (kmacro-name-last-macro name)         ; use this name for the macro
  (find-file user-init-file)            ; open ~/.emacs or other user init file
  (goto-char (point-max))               ; go to the end of the .emacs
  (newline)                             ; insert a newline
  (insert-kbd-macro name)               ; copy the macro
  (newline)                             ; insert a newline
  (switch-to-buffer nil))               ; return to the initial buffer

;;; FONTS

(defun xiaolong/buffer-face-mode-variable-width ()
  (interactive)
  ;; "DejaVu Sans:weight=normal:height=110:antialias=1"
  (setq buffer-face-mode-face
        '(:family "DejaVu Sans" :height 110 :width semi-condensed :weight normal))
  (buffer-face-mode))

(defun xiaolong/buffer-face-mode-same-width ()
  (interactive)
  ;; "DejaVu Sans Mono:weight=normal:height=110:antialias=1"
  (setq buffer-face-mode-face
        '(:family "DejaVu Sans Mono" :height 140 :width semi-condensed :weight normal))
  (buffer-face-mode))

;;; DATA AND TIME

(defun now ()
  "Insert the current datetime."
  (interactive)
  (insert (format-time-string "%Y-%m-%d %H:%M:%S (%A)"))) ;

(defun today ()
  "Insert current date."
  (interactive)
  (insert (format-time-string "%A %Y-%m-%d")))

;;; INDENT

(defun xiaolong/indent-region (N)
  "Indent a region by a number (as N) indentation units."
  (interactive "p")
  (if (use-region-p)
      (progn (indent-rigidly (region-beginning) (region-end) (* N 4))
             (setq deactivate-mark nil))
    (self-insert-command N)))

(defun xiaolong/unindent-region (N)
  "Unindent a region by a number (as N) indentation units."
  (interactive "p")
  (if (use-region-p)
      (progn (indent-rigidly (region-beginning) (region-end) (* N -4))
             (setq deactivate-mark nil))
    (self-insert-command N)))

(defun xiaolong/indent-buffer ()
  "Indent current buffer according to major mode."
  (interactive)
  (indent-region (point-min) (point-max)))

;;; TOGGLE CASE

;; This function toggles the case of the selected text.
(defun xiaolong/toggle-case ()
  "Toggle case."
  (interactive)
  (when (region-active-p)
    (let
      ((i 0)
       (return-string "")
       (input (buffer-substring-no-properties (region-beginning) (region-end))))
      (while (< i (- (region-end) (region-beginning)))
        (let
          ((current-char (substring input i (+ i 1))))
          (if
            (string= (substring input i (+ i 1)) (downcase (substring input i (+ i 1))))
            (setq return-string
              (concat return-string (upcase (substring input i (+ i 1)))))
            (setq return-string
              (concat return-string (downcase (substring input i (+ i 1)))))))
        (setq i (+ i 1)))

    (delete-region (region-beginning) (region-end))
    (insert return-string))))

;;; REVERT

;; Source: http://www.emacswiki.org/emacs-en/download/misc-cmds.el
(defun xiaolong/revert-buffer-no-confirm ()
    "Revert buffer without confirmation."
    (interactive)
    (revert-buffer :ignore-auto :noconfirm))

;;; BUFFER MANAGEMENT

(defun xiaolong/close-all-buffers ()
  "Close all open buffers."
  (interactive)
  (mapc 'kill-buffer (buffer-list)))

(defun xiaolong/kill-other-buffers ()
  "Kill all other buffers."
  (interactive)
  (mapc 'kill-buffer (delq (current-buffer) (buffer-list))))

;;; JSON

(defun xiaolong/beautify-json ()
  "Display JSON in a pretty way."
  (interactive)
  (let ((b (if mark-active (min (point) (mark)) (point-min)))
        (e (if mark-active (max (point) (mark)) (point-max))))
    (shell-command-on-region b
                             e
                             "python -mjson.tool" (current-buffer) t)))

;;; CONFIG

(defun xiaolong/open-config ()
  "Open the Emacs init config."
  (interactive)
  (let ((config-file-path "~/.emacs.d/init.el"))
    (find-file config-file-path)))

;;; OTHER

(defun xiaolong/describe-last-function()
  (interactive)
  (describe-function last-command))

(defun xiaolong/reverse-transpose-sexps (arg)
  (interactive "*p")
  (transpose-sexps (- arg))
  ;; when transpose-sexps can no longer transpose, it throws an error and code
  ;; below this line won't be executed. So, we don't have to worry about side
  ;; effects of backward-sexp and forward-sexp.
  (backward-sexp (1+ arg))
  (forward-sexp 1))  ; transpose-sexps already exists

(defun xiaolong/get-package-version (name)
  (when (member name package-activated-list)
    (package-desc-vers (cdr (assoc name package-alist)))))

;; ESHELL

(defun eshell/e (file)
  "Open the FILE in the current buffer."
  (find-file file))

(defun eshell/ee (file)
  "Open the FILE in a new buffer."
  (find-file-other-window file))

(defun eshell/clear ()
  "Clear the eshell buffer."
  (let ((inhibit-read-only t))
    (erase-buffer)
    (eshell-send-input)))

(defun eshell/x ()
  "Exit the shell."
  (insert "exit")
  (eshell-send-input)
  (delete-window))

(defun eshell-org-file-tags ()
  "Help the eshell parse the text the point is currently on, looking for parameters surrounded in single quotes.  Return a function that takes a FILE and return nil if the file given to it does not contain the 'org-mode' #+TAGS: entry specified."

  ;; Step 1. Parse the eshell buffer for our tag between quotes
  ;;         Make sure to move point to the end of the match:
  (if (looking-at "'\\([^)']+\\)'")
      (let* ((tag (match-string 1))
             (reg (concat "^#\\+TAGS:.* " tag "\\b")))
        (goto-char (match-end 0))

        ;; Step 2. Return the predicate function:
        ;;         Careful when accessing the `reg' variable.
        `(lambda (file)
           (with-temp-buffer
             (insert-file-contents file)
             (re-search-forward ,reg nil t 1))))
    (error "The `T' predicate takes an org-mode tag value in single quotes.")))

(defun eshell/ll ()
  "Print all files with permissions in a list."
  (insert "ls -al")
  (eshell-send-input))
(defun eshell/l ()
  "Print all files with permissions in a list."
  (insert "ls -CF")
  (eshell-send-input))

(defun eshell/did ()
  "Open the did file."
  (interactive)
  (find-file "~/development/Notes/did.org")
  (let ((current-date-time-format "%Y-%m-%d: %a %b %d: %H:%M:%S %Z"))
    ;; insert an additional new line if the current line is not empty
    (if (save-excursion (beginning-of-line)
                        (looking-at "[[:space:]]*$"))
        (insert "\n")
      (insert "\n\n"))
    (insert (concat "* "
                    (format-time-string current-date-time-format (current-time))))
    (insert "\n")))

;;; =============
;;; CUSTOM COLORS
;;; =============

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(cursor ((t (:foreground "orange red" :inverse-video t :underline t :weight bold))))
 '(hl-line ((t (:extend t))))
 '(org-hide ((t (:foreground "gray30"))))
 '(region ((t (:inherit highlight :extend t :background "#434546" :foreground "spring green")))))

;;; =============
;;; OPTIMIZATIONS
;;; =============

;; deactivate a time consuming step we might not need, to improve long
;; line performance
;; (setq-default bidi-display-reordering nil)
(setq bidi-paragraph-direction 'left-to-right)

;;; ==============
;;; EMACS SETTINGS
;;; ==============

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(cursor-type '(hbar . 3))
 '(custom-enabled-themes '(sanityinc-tomorrow-eighties))
 '(custom-safe-themes
   '("628278136f88aa1a151bb2d6c8a86bf2b7631fbea5f0f76cba2a0079cd910f7d" "1711947b59ea934e396f616b81f8be8ab98e7d57ecab649a97632339db3a3d19" "7675ffd2f5cb01a7aab53bcdd702fa019b56c764900f2eea0f74ccfc8e854386" "ae65ccecdcc9eb29ec29172e1bfb6cadbe68108e1c0334f3ae52414097c501d2" "7923541211298e4fd1db76c388b1d2cb10f6a5c853c3da9b9c46a02b7f78c882" "9abe2b502db3ed511fea7ab84b62096ba15a3a71cdb106fd989afa179ff8ab8d" "95b0bc7b8687101335ebbf770828b641f2befdcf6d3c192243a251ce72ab1692" default))
 '(global-hl-line-mode t)
 '(package-selected-packages
   '(line-reminder ob-prolog color-theme-sanityinc-tomorrow kaolin-themes zeno-theme melancholy-theme afternoon-theme gruber-darker-theme fuel forth-mode ob-async auto-complete company dockerfile-mode editorconfig flycheck jinja2-mode undercover json-reformat json-mode neotree python-environment undo-tree monokai-theme exec-path-from-shell erlang ox-gfm typescript-mode goto-chg multiple-cursors sml-mode toml-mode web-mode yaml-mode yasnippet magit markdown-mode s pos-tip paredit faceup racket-mode))
 '(warning-suppress-types '((comp))))
(put 'downcase-region 'disabled nil)
(put 'erase-buffer 'disabled nil)
(put 'upcase-region 'disabled nil)
(set-cursor-color "#13ff00")

(server-start)
